"""
========
m.parser
========

The actual template parsing tools.

"""

import sys

from m.common import EndNode
from m.config import *
from m.core import Node, NodeList, Tag
from m.exceptions import InvalidTag, UnclosedTag, UnexpectedTag

class Parser:
    """
    ======
    Parser
    ======

    The parser itself, which is used to convert a list of tokens into a NodeList of nodes, which can then be rendered directly to plain strings, concluding the template rendering process.
    
    """
    def __init__(self, tokens):
        """
        Parser.__init__
        ===============

        __init__(self, tokens) -> Parser instance
        Instantiate the parsing class, storing the sent ``tokens``.
        
        """
        self.tokens = tokens

    def parse(self):
        """
        Parser.parse
        ============

        parse(self) -> NodeList instance
        Do the actual parsing.
        
        """
        nodes = NodeList()
        self.sub = []
        while self.tokens:
            token = self.pop_token()
            if not issubclass(token.Node, Tag):  # alright, simple enough.
                nodes.append(token.Node(token.contents))
            else:  # we're dealing with a tag.
                assert token.Node != Tag, InvalidTag("An invalid tag was detected: %s" % token.contents)
                self.nest = [[token.Node, token.contents, NodeList()]]
                self.depth = 1
                while self.depth and self.tokens:
                    tok = self.pop_token()
                    if tok.id in (TEXT_ID, VARIABLE_ID):
                        self.nest[-1][2].append(tok.Node(tok.contents))
                    else:
                        if tok.Node == Tag:  # unrecognised tag, perhaps it's a subtag?
                            if self.sub:
                                self.end_tag()

                            assert tok.contents.split()[0] in self.nest[-1][0].takes_subs, UnexpectedTag("Unexpected tag '%s' detected inside %s." % (tok.contents, self.nest[-1][0]))

                            self.sub.append((tok.contents, self.depth))
                            self.nest[-1].append(self.nest[-1][0](self.nest[-1][1], self.nest[-1][2]))
                            self.nest[-1][2] = NodeList()
                        elif tok.Node == EndNode:  # a tag is being closed.
                            if not self.end_tag(True):
                                break
                        else:  # a self.nested tag, alright.
                            self.nest.append([tok.Node, tok.contents, NodeList()])
                            self.depth += 1

                if self.depth:
                    raise UnclosedTag("A tag was left unclosed")

                if len(self.nest[-1]) > 3:
                    inst = self.nest[-1][3]
                else:
                    inst = self.nest[-1][0](self.nest[-1][1], self.nest[-1][2])

                nodes.append(inst)

        return nodes

    def end_tag(self, tag=False):
        """
        Parser.end_tag
        ==============

        end_tag(self, tag=False) -> boolean
        End an open tag or subtag. Could be implemented in a better way, which I'll get around to later. If ``tag`` is True (which is the case when this is an actual ``end`` token), the depth is decreased.
        
        """
        if self.sub and self.depth == self.sub[-1][1]:
            self.nest[-1][3].add_sub(self.sub[-1][0], self.nest[-1][2])
            inst = self.nest[-1][3]
            del self.sub[-1]
        else:
            inst = self.nest[-1][0](self.nest[-1][1], self.nest[-1][2])

        if tag:
            if len(self.nest) > 1:
                self.nest[-2][2].append(inst)
                del self.nest[-1]
                self.depth -= 1
            else:
                self.depth -= 1
                return False

        return True

    def pop_token(self):
        """
        Parser.pop_token
        ================

        pop_token(self) -> Token instance
        Pop a token from the token stack stored.
        
        """
        return self.tokens.pop(0)
